/**
 * Covalent Server Code File
 * 
 * File:        socket_helper.c
 * Brief:       BRIEF
 * Author:      Chen Kangbing
 * Email:       ckblau@outlook.com
 * Modified:    2021/10/08 17:24:17
 *
 */


#include "socket_helper.h"

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


const int BUFFER_SIZE_SINGLE = 1024 * sizeof(char);


int socket_init_server(ServerTypeDef *psrv) {
    int addrlen;
    int on = 1;
    int rv = -1;

    if (!psrv) return -10;

    psrv->socket_srvfd = socket(AF_INET, SOCK_STREAM, 0);
    if (psrv->socket_srvfd < 0) {
        printf("Failed to create socket: %s\n", strerror(errno));
        return -1;
    }
    printf("Socket[%d] created successfully.\n", psrv->socket_srvfd);

    setsockopt(psrv->socket_srvfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    addrlen = sizeof(psrv->socket_srvaddr);
    memset(&psrv->socket_srvaddr, 0, addrlen);
    psrv->socket_srvaddr.sin_family = AF_INET;
    psrv->socket_srvaddr.sin_port = htons(psrv->socket_port);
    psrv->socket_srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 监听本机所有IP*/
    // inet_aton("192.168.0.16", &servaddr.sin_addr); /* 监听指定ip */

    rv = bind(psrv->socket_srvfd, (struct sockaddr *)&psrv->socket_srvaddr, addrlen);
    if (rv < 0) {
        printf("Failed to bind socket[%d] on port[%d], errno [%d]: %s\n", 
               psrv->socket_srvfd, psrv->socket_port, errno, strerror(errno));
        return -2;
    }

    listen(psrv->socket_srvfd, 13);
    printf("Now listening on port[%d] ...\n", psrv->socket_port);

    return 0;
}

int socket_loop_listen(ServerTypeDef *psrv, WorkerTaskTypeDef *ptask) {
    socklen_t len;
    // struct timeval timeout = {100, 0};    // 1, 0 == 1s
    // int rv;

    if (!ptask) return -10;

    // printf("Waiting for incoming client ...\n");

    ptask->socket_clifd = accept(psrv->socket_srvfd, (struct sockaddr *)&ptask->socket_cliaddr, &len);
    if (ptask->socket_clifd < 0) {
        printf("Failed to accept incoming client, errno [%d]: %s\n", 
               errno, strerror(errno));
        return ptask->socket_clifd;
    }
    // printf("Incoming client <---[%s:%d]\n",
    //        inet_ntoa(ptask->socket_cliaddr.sin_addr), 
    //        ntohs(ptask->socket_cliaddr.sin_port));
        
    // rv = setsockopt(ptask->socket_clifd, 
    //                 SOL_SOCKET, SO_SNDTIMEO, 
    //                 (const char *)&timeout, sizeof(timeout));
    // if (rv < 0) return 1;
    // rv = setsockopt(ptask->socket_clifd, 
    //                 SOL_SOCKET, SO_RCVTIMEO, 
    //                 (const char *)&timeout, sizeof(timeout));
    // if (rv < 0) return 1;

    return 0;
}

int transmit_down(WorkerThreadTypeDef *pworker) {
    int rv;
    int buf_size = BUFFER_SIZE_SINGLE;
    int current = 0;
    int size_rest;

    if (pworker->down_buffer) {
        free(pworker->down_buffer);
        pworker->down_buffer = NULL;
    }

    pworker->down_buffer = malloc(buf_size);
    pworker->down_buffer_size = -1;
    
    if (!pworker) return -10;

    while (1) {
        size_rest = buf_size - current;
        if (size_rest < BUFFER_SIZE_SINGLE) {
            buf_size += BUFFER_SIZE_SINGLE;
            pworker->down_buffer = realloc(pworker->down_buffer, buf_size);
            size_rest = buf_size - current;
        }
        char *buf = pworker->down_buffer + current;

        rv = recv(pworker->task.socket_clifd, buf, size_rest, 0);
        if (rv < 0) {
            printf("thread [%ld]: "
                   "Failed[%d] to receive data from client socket[%d], errno [%d]: %s.\n"
                   "Bad connection.\n", 
                   pworker->thread_tid,
                   rv, pworker->task.socket_clifd, errno, strerror(errno));
            close(pworker->task.socket_clifd);
            return -1;
        } else if (rv == 0) {
            printf("thread [%ld]: "
                   "Disconnected from client socket[%d].\n"
                   "Bad connection.\n", 
                   pworker->thread_tid,
                   pworker->task.socket_clifd);
            close(pworker->task.socket_clifd);
            return -2;
        } else if (rv > 0) {
            current += rv;
            if (rv < size_rest) {
                pworker->down_buffer_size = current;
                break;
            }
        }
    }
    pworker->down_buffer[pworker->down_buffer_size] = 0;

    // printf("thread [%ld]: "
    //        "Down transmission finished.\n"
    //        "Recieved %d bytes from client socket.\n", 
    //        pworker->thread_tid,
    //        pworker->down_buffer_size);

    return 0;
}

int transmit_up(WorkerThreadTypeDef *pworker) {
    int rv;
    int current = 0;
    int size_rest = -1;
    char *buf;

    if (!pworker) return -10;

    while (1) {
        size_rest = pworker->up_buffer_size - current;
        if (size_rest <= 0) {
            break;
        }
        if (size_rest > BUFFER_SIZE_SINGLE) {
            size_rest = BUFFER_SIZE_SINGLE;
        }
        buf = pworker->up_buffer + current;

        rv = send(pworker->task.socket_clifd, buf, size_rest, 0);
        if (rv < 0) {
            printf("thread [%ld]: "
                   "Failed to send data to client socket[%d], errno [%d]: %s.\n"
                   "The thread will now exit.\n",
                   pworker->thread_tid,
                   pworker->task.socket_clifd, errno, strerror(errno));
            close(pworker->task.socket_clifd);
            return -1;
        } else if (rv == 0) {
            printf("thread [%ld]: "
                   "Disconnected from socket[%d].\n"
                   "The thread will now exit.\n",
                   pworker->thread_tid,
                   pworker->task.socket_clifd);
            close(pworker->task.socket_clifd);
            return -2;
        } else if (rv > 0) {
            current += rv; 
        }
    }

    if (current != pworker->up_buffer_size) {
        printf("thread [%ld]: "
               "Transferred data length mismatch.\n"
               "%d bytes transferred, %d bytes expected.\n",
               pworker->thread_tid,
               current, pworker->up_buffer_size);
        close(pworker->task.socket_clifd);
        return -3;
    }

    if (pworker->up_buffer) {
        free(pworker->up_buffer);
        pworker->up_buffer = NULL;
    }
    
    // printf("thread [%ld]: "
    //        "Up transmission finished.\n"
    //        "Transferred %d bytes to client socket.\n",
    //        pworker->thread_tid,
    //        current);
    return 0;
}

void task_shutdown(WorkerTaskTypeDef *ptask) {
    // char buf[BUFFER_SIZE_SINGLE];
    // int rv;

    if (!ptask) return;
    if (!ptask->socket_clifd) return;

    shutdown(ptask->socket_clifd, SHUT_WR);
    shutdown(ptask->socket_clifd, SHUT_RD);
    // wait until client disconnect 
    // rv = recv(ptask->socket_clifd, buf, BUFFER_SIZE_SINGLE, 0);
    // if (rv < 0) {
    //     printf("Failed[%d] to shutdown client socket[%d], errno [%d]:%s.\n"
    //            "The data sent may be corrupted.\n",
    //            rv, ptask->socket_clifd, errno, strerror(errno));
    // } 
    close(ptask->socket_clifd);
}

void worker_reset(WorkerThreadTypeDef *pworker) {
    if (!pworker) return;
    if (pworker->down_buffer) {
        free(pworker->down_buffer);
        pworker->down_buffer = NULL;
    }
    if (pworker->up_buffer) {
        free(pworker->up_buffer);
        pworker->up_buffer = NULL;
    }
}

void socket_deinit_server(ServerTypeDef *psrv) {
    if (!psrv) return;
    close(psrv->socket_srvfd);
}
